#include "scrambled_symbols.h"
#include "priv/challengeimpl.hxx"
#include "challenge.hxx"
#include "myrandom.h"
#include "buffer.hxx"

#include "openssl/sha.h" 
#include "openssl/dsa.h" 

using namespace std;

ChallengeImpl::ChallengeImpl(const tByte *remote_public_key, tUInt32 remote_public_key_len, const tByte *private_key, tUInt32 private_key_len, const tByte *challenge_key, tUInt32 challenge_key_len)
  : _data(0), _remote_public_key(remote_public_key), _remote_public_key_len(remote_public_key_len), _private_key(private_key), _private_key_len(private_key_len),
    _key(), _ivec()

{
  tByte buf[20];;

  SHA1(challenge_key,challenge_key_len,buf);

  _key=(BF_KEY *) malloc(sizeof(BF_KEY));

  BF_set_key(_key,challenge_key_len,challenge_key);

  // Computing initialization vector. Values are obtained from the input key
  // from Xor operations of bytes from the SHA1 sum.
  _ivec=new tByte[8];

  _ivec[0]=buf[0]^buf[8]^buf[16];
  _ivec[1]=buf[1]^buf[9]^buf[17];
  _ivec[2]=buf[2]^buf[10]^buf[18];
  _ivec[3]=buf[3]^buf[11]^buf[19];
  _ivec[4]=buf[4]^buf[12]^buf[19];
  _ivec[5]=buf[5]^buf[13]^buf[18];
  _ivec[6]=buf[6]^buf[14]^buf[17];
  _ivec[7]=buf[7]^buf[15]^buf[16];
  
  Instanciate();
}

ChallengeImpl::~ChallengeImpl()
{
  Clear();

  if (_key)
    {
      free(_key);
      _key=0;
    }

  if (_ivec)
    {
      delete [] _ivec;
      _ivec=0;
    }
}

void ChallengeImpl::Clear()
{
  if (_data)
    {
      memset(_data,0,sizeof(tChallenge));
      free(_data);
      _data=0;
    }
}

bool ChallengeImpl::Instanciate()
{
  if (_data)
    {
      return false;
    }
  _data = (tChallenge *) malloc(sizeof(tChallenge));

  if (_data==0) 
    {
      return false;
    }
  memset(_data,0,sizeof(tChallenge));
  return true;
}

bool ChallengeImpl::Generate()
{
  tUInt32 i;

  // If a previous array existed, then we dismissed it!
  Clear();

  // Instanciate (allocate) memory space for the new challenge data.
  if (!Instanciate())
    {
      return false;
    }

  // Filling data with (pseudo) random values.
  for (i=0; i<CHALLENGELENGTH; i++)
    {
      _data->data[i]=(tByte) myrandom(256);
    }
  _data->data_len=CHALLENGELENGTH;
  
  Encrypt();

  if (!Sign())
    {
      Clear();
      return false;
    }

  return true;
}

void ChallengeImpl::Decrypt() const
{
  tByte ivec[8];
  memcpy(&ivec[0],&_ivec[0],8);
  BF_cbc_encrypt(&(_data->data[0]),&(_data->data[0]),CHALLENGELENGTH, _key, &ivec[0], BF_DECRYPT);
}

void ChallengeImpl::Encrypt() const
{
  tByte ivec[8];
  memcpy(&ivec[0],&_ivec[0],8);
  BF_cbc_encrypt(&(_data->data[0]),&(_data->data[0]),CHALLENGELENGTH, _key, &ivec[0], BF_ENCRYPT);
}

bool ChallengeImpl::Sign()
{
  DSA *dsa;
  bool res;
  const tByte *p_private_key;
  tByte digest[SHA_DIGEST_LENGTH];
  tUInt32 i;

  res=true;

  p_private_key=_private_key;

  // Computing data digest.
  SHA1(&_data->data[0],_data->data_len,&digest[0]);

  // Restoring private key.
  dsa=d2i_DSAPrivateKey(0, &p_private_key, _private_key_len);

  // Signing data.
  if (1!=DSA_sign(0,&digest[0],SHA_DIGEST_LENGTH,&_data->signature[0],&_data->signature_len,dsa))
    {
      res=false;
    }

  // Filling rest of signature with default value 0xFF.
  for (i=_data->signature_len; i<MAXCHALLENGESIGNATURELEN; i++)
    {
      _data->signature[i]=0xFF;
    }

  DSA_free(dsa);

  return res;
}

bool ChallengeImpl::Verify()
{
  DSA *dsa;
  bool res;
  const tByte *p_public_key;
  tByte digest[SHA_DIGEST_LENGTH];

  res=true;

  p_public_key=_remote_public_key;

  // Computing digest of the given data.
  SHA1(&_data->data[0],_data->data_len,&digest[0]);

  // Restoring public key.
  dsa=d2i_DSAPublicKey(0, &p_public_key, _remote_public_key_len);

  // Verifying signature.
  if (1!=DSA_verify(0,&digest[0],SHA_DIGEST_LENGTH,&_data->signature[0],_data->signature_len,dsa))
    {
      res=false;
    }

  DSA_free(dsa);

  return res;
}

bool ChallengeImpl::IsEqual(const ChallengeImpl &challenge) const
{
  tUInt32 i;
  tBool result;

  result=true;

  if (_data->data_len!=challenge._data->data_len)
    {
      return false;
    }
  
  Decrypt();
  challenge.Decrypt();

  for (i=0; i<_data->data_len; i++)
    {
      if (_data->data[i]!=challenge._data->data[i]) 
	{
	  result=false;
	  break;
	}
    }

  challenge.Encrypt();
  Encrypt();

  return result;
}

bool ChallengeImpl::Resolve()
{
  tUInt32 i;
  tUInt32 acc;
  tUInt32 x;
  tByte digest[SHA_DIGEST_LENGTH];

  acc=19;

  Decrypt();

  for (i=0; i<_data->data_len; i++)
    {
      acc+=i+1;
      x=i-_data->data_len/2;
      acc-=3*x;
      x*=x;
      acc+=_data->data[i]*x;
      x*=x;
      acc-=3*x;
      _data->data[i]=(tByte) acc&0xFF - (acc>>8)&0xFF + (acc>>16)&0xFF - (acc>>24)&0xFF;
    }

  SHA1(&_data->data[0],_data->data_len,&digest[0]);

  for (i=0; i<_data->data_len; i++)
    {
      if (i<SHA_DIGEST_LENGTH)
	{
	  _data->data[i]=digest[i];
	}
      else
	{
	  _data->data[i]=0xFF;
	}
    }

  _data->data_len=SHA_DIGEST_LENGTH;

  Encrypt();

  return true;
}

const tByte *ChallengeImpl::Content() const
{
  return (tByte *) _data;
}

tByte *ChallengeImpl::Content()
{
  return (tByte *) _data;
}

///////////////////////////////////////////////////////////////////////////////
//
///////////////

Challenge::Challenge(const tByte *public_key, tUInt32 public_key_len, const tByte *private_key, tUInt32 private_key_len, const tByte *challenge_key, tUInt32 challenge_key_len)
  : _impl(0)
{
  _impl = new ChallengeImpl(public_key,public_key_len,private_key,private_key_len,challenge_key,challenge_key_len);
}

Challenge::~Challenge()
{
  if (_impl)
    {
      delete _impl;
      _impl=0;
    }
}

bool Challenge::Generate()
{
  return _impl->Generate();
}

bool Challenge::operator !=(const Challenge &challenge) const
{
  return !_impl->IsEqual(*(challenge._impl));
}

bool Challenge::Resolve()
{
  return _impl->Resolve();
}

bool Challenge::Sign()
{
  return _impl->Sign();
}

bool Challenge::Verify()
{
  return _impl->Verify();
}

const tByte *Challenge::Content() const
{
  return _impl->Content();
}

tByte *Challenge::Content()
{
  return _impl->Content();
}

Buffer &Buffer::operator <<(const Challenge &challenge)
{
  tUInt32 counter;
  const tByte *pdata;

  pdata=challenge.Content();
  counter=0;
  while(counter<sizeof(tChallenge))
    {
      (*this)<<(tByte) *pdata;
      pdata++;
      counter++;
    }
  return *this;
}

Buffer &Buffer::operator >>(Challenge *challenge)
{
  tUInt32 counter;
  tByte *pdata;

  pdata=challenge->Content();
  counter=0;
  while(counter<sizeof(tChallenge))
    {
      (*this)>>pdata;
      pdata++;
      counter++;
    }
  return *this;
}


